# -*- coding: utf-8 -*-

# Minneo -- A classic Memory game for mobile devices
#
# Copyright (C) 2010-2012 Valéry Febvre <vfebvre@easter-eggs.com>
# http://code.google.com/p/minneo/
#
# This file is part of Minneo.
#
# Minneo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Minneo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Minneo -- A classic Memory game for mobile devices
"""

import alsaaudio, wave
import cPickle, os.path, random
import ecore, elementary

APP_VERSION = '1.0.3'
APP_NAME = 'Minneo'
#DATA_DIR = '/usr/share/minneo/'
DATA_DIR = '../data'
GUI_THEME = 'default'
GUI_THEME_EDJ = os.path.join(DATA_DIR, 'themes', GUI_THEME, 'minneo.edj')

LEVELS = ['4x3', '4x4', '5x4', '6x5', '6x6']
MODES = ['Single Player', 'Two Players']
DEFAULT_SETTINGS = {
    'level':      LEVELS[0],
    'theme':      'animals',
    'mode':       1,
    'player1':    '',
    'player2':    '',
    'sounds':     True,
    'fullscreen': False,
    'rotation':   0,
    }
CARDS = {}
for root, dirs, files in os.walk(os.path.join(DATA_DIR, 'cards')):
    theme_name = os.path.basename(root)
    if theme_name != 'cards':
        for i, file_name in enumerate(files):
            files[i] = os.path.join(DATA_DIR, root, file_name)
        CARDS[theme_name] = files

#
# Config
#

class Config():
    def __init__(self):
        cfg_dir = os.path.join(os.path.expanduser("~"), ".config/%s" % APP_NAME.lower())
        if not os.path.exists(cfg_dir):
            os.makedirs(cfg_dir, 0755)
        
        self.cfg_path = os.path.join(cfg_dir, '%s.conf' % APP_NAME.lower())
        if not os.path.exists(self.cfg_path):
            self.data = DEFAULT_SETTINGS
        else:
            self.data = cPickle.load(open(self.cfg_path, 'r'))

        self.is_dirty = False

    def save(self):
        if self.is_dirty:
            cPickle.dump(self.data, open(self.cfg_path, 'w+'))
            self.is_dirty = False

    def get(self, name):
        if name not in self.data.keys():
            # a new setting not available in user settings yet
            self.data[name] = DEFAULT_SETTINGS[name]
        return self.data[name]

    def set(self, name, value):
        if self.data[name] != value:
            self.data[name] = value
            self.is_dirty = True

cfg = Config()

#
# Icon
#

class Icon(elementary.Icon):
    def __init__(self, parent, image):
        elementary.Icon.__init__(self, parent)
        self.image_set(image)

    def image_set(self, image):
        self.file_set(os.path.join(DATA_DIR, 'images', image))


#
# Dialogs
#

class ConfirmDialog(object):
    def __init__(self, main, title = 'Confirm ?'):
        self.main = main
        self.win = None
        self.title = title

    def _close(self, *args):
        self.win.hide()

    def _confirm(self, *args):
        self._close()
        self.confirm_cb(**self.confirm_cb_args)

    def _create(self):
        self.win = elementary.InnerWindow(self.main.win)
        self.win.style_set("minimal_vertical")

        box = elementary.Box(self.win)
        self.win.content_set(box)
        box.show()

        frame = elementary.Frame(self.win)
        frame.text_set(self.title)
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, -1)
        box.pack_start(frame)
        frame.show()

        self.entry_title = elementary.Entry(self.win)
        self.entry_title.line_wrap_set(True)
        self.entry_title.editable_set(False)
        self.entry_title.entry_set('')
        self.entry_title.size_hint_weight_set(1, 1)
        frame.content_set(self.entry_title)
        self.entry_title.show()

        box_actions = elementary.Box(self.win)
        box_actions.horizontal_set(True)
        box_actions.homogenous_set(True)
        box_actions.size_hint_weight_set(1, 0)
        box_actions.size_hint_align_set(-1, -1)
        box.pack_end(box_actions)
        box_actions.show()

        btn_no = elementary.Button(self.win)
        btn_no.text_set("No")
        btn_no.size_hint_weight_set(1, 1)
        btn_no.size_hint_align_set(-1, -1)
        btn_no.callback_clicked_add(self._close)
        box_actions.pack_end(btn_no)
        btn_no.show()

        btn_yes = elementary.Button(self.win)
        btn_yes.text_set("Yes")
        btn_yes.size_hint_weight_set(1, 1)
        btn_yes.size_hint_align_set(-1, -1)
        btn_yes.callback_clicked_add(self._confirm)
        box_actions.pack_end(btn_yes)
        btn_yes.show()

    def open(self, message, confirm_cb, **kwargs):
        if self.win is None:
            self._create()

        self.entry_title.entry_set(message)
        self.confirm_cb = confirm_cb
        self.confirm_cb_args = kwargs
        self.win.show()


class InfoDialog(object):
    def __init__(self, main):
        self.main = main
        self.win = None

    def _close(self, *args):
        self.win.hide()
        self.scroller.region_show(0, 0, 0, 0)

    def _create(self):
        self.win = elementary.InnerWindow(self.main.win)

        box = elementary.Box(self.win)
        self.win.content_set(box)
        box.show()

        self.scroller = elementary.Scroller(self.win)
        self.scroller.bounce_set(False, True)
        self.scroller.size_hint_weight_set(1, 1)
        self.scroller.size_hint_align_set(-1, -1)
        box.pack_end(self.scroller)
        self.scroller.show()

        self.entry = elementary.Entry(self.win)
        self.entry.line_wrap_set(True)
        self.entry.editable_set(False)
        self.entry.entry_set('')
        self.entry.size_hint_weight_set(1, 1)
        self.scroller.content_set(self.entry)
        self.entry.show()

        btn = elementary.Button(self.win)
        btn.text_set("Close")
        btn.size_hint_weight_set(1, 0)
        btn.size_hint_align_set(-1, -1)
        btn.callback_clicked_add(self._close)
        box.pack_end(btn)
        btn.show()

    def open(self, info):
        if self.win is None:
            self._create()

        self.entry.entry_set(info)
        self.win.show()


class NotifyDialog(object):
    def __init__(self, main):
        self.main = main
        self.notify = None

    def _close(self, btn):
        self.notify.hide()

    def _create(self):
        self.notify = elementary.Notify(self.main.win)
        self.notify.repeat_events_set(False)
        self.notify.orient_set(elementary.ELM_NOTIFY_ORIENT_LEFT)

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        box.horizontal_set(True)
        self.notify.content_set(box)
        box.show()

        self.label = elementary.Label(self.main.win)
        box.pack_end(self.label)
        self.label.show()

        btn = elementary.Button(self.main.win)
        btn.text_set("Close")
        btn.callback_clicked_add(self._close)
        box.pack_end(btn)
        btn.show()

    def open(self, message, timeout = 5):
        if self.notify is None:
            self._create()

        self.label.text_set(message)
        self.notify.timeout_set(timeout if timeout is not None else 0)
        self.notify.show()


#
# Playground
#

class Playground(object):
    def __init__(self, main):
        self.board = []
        self.completed = False
        self.level = None
        self.icons_cards = []
        self.reversed_cards = []
        self.found_pairs = []
        self.nb_found_pairs = [0, 0]
        self.turns = 0

        self.main = main
        self.confirm_dlg = ConfirmDialog(self.main)
        self.info_dlg = InfoDialog(self.main)
        self.notify_dlg = NotifyDialog(self.main)

        self.box = elementary.Box(self.main.win)

        # turns
        self.box_turns = elementary.Box(self.main.win)
        self.box_turns.size_hint_weight_set(1, 0)
        self.box_turns.size_hint_align_set(-1, -1)
        self.box_turns.horizontal_set(True)
        self.box.pack_end(self.box_turns)
        self.box_turns.show()

        self.icon_player1 = elementary.Icon(self.main.win)
        self.icon_player1.size_hint_weight_set(1, 1)
        self.icon_player1.size_hint_align_set(-1, -1)

        self.label_player1_turns = elementary.Label(self.main.win)
        self.label_player1_turns.scale_set(.75)
        self.label_player1_turns.size_hint_weight_set(1, 1)
        self.label_player1_turns.size_hint_align_set(0, 0.5)
        self.label_player1_turns.show()

        self.label_player2_turns = elementary.Label(self.main.win)
        self.label_player2_turns.scale_set(.75)
        self.label_player2_turns.size_hint_weight_set(1, 1)
        self.label_player2_turns.size_hint_align_set(1, 0.5)

        self.icon_player2 = elementary.Icon(self.main.win)
        self.icon_player2.size_hint_weight_set(1, 1)
        self.icon_player2.size_hint_align_set(-1, -1)

        # board
        self.table = elementary.Table(self.main.win)
        self.table.size_hint_weight_set(1, 1)
        self.table.size_hint_align_set(-1, -1)
        self.box.pack_end(self.table)
        self.table.show()

        # button new game
        self.btn_new = elementary.Button(self.main.win)
        self.btn_new.size_hint_align_set(-1, -1)
        self.btn_new.text_set('New Game')
        self.box.pack_end(self.btn_new)
        self.btn_new.callback_clicked_add(self.new)
        self.btn_new.show()

        self.main.pager.item_simple_push(self.box)

    def build_board(self, btn = None, empty = False):
        self.level = cfg.get('level').split('x')
        self.level[0] = int(self.level[0])
        self.level[1] = int(self.level[1])

        if empty:
            # build empty board
            for i in range(self.level[0]):
                for j in range(self.level[1]):
                    icon = elementary.Icon(self.main.win)
                    icon.file_set(GUI_THEME_EDJ, 'minneo/minneo')
                    icon.size_hint_weight_set(1, 1)
                    icon.size_hint_align_set(-1, -1)
                    self.table.pack(icon, i, j, 1, 1)
                    icon.show()
            return

        self.nb_pairs = (self.level[0] * self.level[1]) / 2
        self.completed = False
        self.turns = 0
        self.reversed_cards = []
        self.found_pairs = []
        self.nb_found_pairs = [0, 0] # foreach player

        # init turns bar
        self.box_turns.unpack_all()
        if cfg.get('mode') == 1:
            # single player
            self.current_player = 0

            self.icon_player1.hide()
            self.box_turns.pack_end(self.label_player1_turns)
            self.label_player1_turns.show()
            self.label_player2_turns.hide()
            self.icon_player2.hide()
        else:
            # 2 players
            self.current_player = random.randint(0, 1)
            self.notify_dlg.open(
                '%s was<br>randomly selected<br>to go first' % (
                    cfg.get('player%d' % (self.current_player + 1)) or 'Player %d' % (self.current_player + 1)
                    )
                )

            self.box_turns.pack_end(self.icon_player1)
            self.icon_player1.show()
            self.box_turns.pack_end(self.label_player1_turns)
            self.box_turns.pack_end(self.label_player2_turns)
            self.label_player2_turns.show()
            self.box_turns.pack_end(self.icon_player2)
            self.icon_player2.show()
            self.box_turns.pack_start(self.icon_player1)
            self.box_turns.pack_end(self.icon_player2)
        self.update_turns()

        # randomly select pairs
        pairs = []
        while len(pairs) < self.nb_pairs:
            rnd = random.randint(0, len(CARDS[cfg.get('theme')]) - 1)
            if rnd not in pairs:
                pairs.append(rnd)
        pairs = pairs * 2

        # randomly put cards in board
        self.board = []
        for i in range(self.level[0]):
            self.board.append([])
            for j in range(self.level[1]):
                rnd = random.randint(0, len(pairs) - 1)
                card_num = pairs[rnd]
                self.board[i].append(card_num)
                pairs.remove(card_num)

        # display board
        self.table.clear(True)
        self.icons_cards = []
        for i in range(self.level[0]):
            self.icons_cards.append([])
            for j in range(self.level[1]):
                icon = elementary.Icon(self.main.win)
                icon.file_set(GUI_THEME_EDJ, 'minneo/reverse')
                icon.size_hint_weight_set(1, 1)
                icon.size_hint_align_set(-1, -1)
                icon.callback_clicked_add(self.reverse_card, i, j)
                self.table.pack(icon, i, j, 1, 1)
                icon.show()
                self.icons_cards[i].append(icon)

    def new(self, btn):
        if len(self.found_pairs) > 0 and len(self.found_pairs) < self.nb_pairs:
            self.confirm_dlg.open('Do you really want to start a new game ?', self.build_board)
        else:
            self.build_board()

    def play_sound(self, sound):
        if not cfg.get('sounds'):
            return

        f = wave.open(os.path.join(DATA_DIR, 'sounds/%s.wav' % sound), 'rb')
        data = f.readframes(320)
        while data:
            self.main.pcm.write(data)
            data = f.readframes(320)
        f.close()

    def promote(self):
        if not self.board:
            self.build_board(empty = True)
        self.main.pager.item_simple_promote(self.box)

    def reverse_card(self, card, i, j):
        if (i, j) in self.reversed_cards:
            return
        if self.board[i][j] in self.found_pairs:
            return

        if len(self.reversed_cards) == 2:
            c1, c2 = self.reversed_cards
            if self.board[c1[0]][c1[1]] != self.board[c2[0]][c2[1]]:
                self.icons_cards[c1[0]][c1[1]].file_set(GUI_THEME_EDJ, 'minneo/reverse')
                self.icons_cards[c2[0]][c2[1]].file_set(GUI_THEME_EDJ, 'minneo/reverse')
            self.reversed_cards = []
        elif len(self.reversed_cards) == 1:
            self.turns += 1
            c = self.reversed_cards[0]
            if self.board[c[0]][c[1]] == self.board[i][j]:
                self.found_pairs.append(self.board[i][j])
                self.nb_found_pairs[self.current_player] += 1
                if len(self.found_pairs) == self.nb_pairs:
                    self.completed = True
            elif cfg.get('mode') == 2:
                self.current_player = (self.current_player + 1) % 2

        self.reversed_cards.append((i, j))
        self.icons_cards[i][j].file_set(CARDS[cfg.get('theme')][self.board[i][j]])
        self.update_turns()

        if self.completed:
            self.play_sound('yeah')
            if cfg.get('mode') == 1:
                # check new high score if mode is 'Single Player'
                self.main.high_scores.check_new(self.turns)
            else:
                # else display resume (winner + pairs found)
                if self.nb_found_pairs[0] == self.nb_found_pairs[1]:
                    msg = 'No winner.'
                elif self.nb_found_pairs[0] > self.nb_found_pairs[1]:
                    msg = 'Bravo %s, you wins!' % (cfg.get('player1') or 'Player 1')
                else:
                    msg = 'Bravo %s, you wins!' % (cfg.get('player2') or 'Player 2')
                for i in range(1, 3):
                    msg += '<br><br>'
                    if self.nb_found_pairs[i - 1] < 2:
                        msg += '%s founds %d pair.' % (
                            cfg.get('player%d' % i) or 'Player %d' % i, self.nb_found_pairs[i - 1])
                    else:
                        msg += '%s founds %d pairs.' % (
                            cfg.get('player%d' % i) or 'Player %d' % i, self.nb_found_pairs[i - 1])
                self.info_dlg.open(msg)

    def update_turns(self):
        if cfg.get('mode') == 1:
            self.label_player1_turns.text_set('Turns: %d' % self.turns)
        else:
            # update smiley icon: who is next?
            if self.completed:
                self.icon_player1.hide()
                self.icon_player2.hide()
            elif self.current_player == 0:
                self.icon_player1.file_set(GUI_THEME_EDJ, 'minneo/player')
                self.icon_player1.show()
                self.icon_player2.hide()
            else:
                self.icon_player1.hide()
                self.icon_player2.file_set(GUI_THEME_EDJ, 'minneo/player')
                self.icon_player2.show()
            # update number of pairs founds
            self.label_player1_turns.text_set(
                '<b>%s</> : %d' % (cfg.get('player1') or 'Player 1', self.nb_found_pairs[0]))
            self.label_player2_turns.text_set(
                '<b>%s</> : %d' % (cfg.get('player2') or 'Player 2', self.nb_found_pairs[1]))


#
# Settings
#

class Settings(object):
    def __init__(self, main):
        self.main = main
        self.box = None

    def build(self):
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_weight_set(1, 1)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, False)
        scroller.size_hint_weight_set(1, 1)
        scroller.size_hint_align_set(-1, -1)
        self.box.pack_end(scroller)
        scroller.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 0)
        box.size_hint_align_set(-1, 0)
        scroller.content_set(box)
        box.show()

        # level
        frame_level = elementary.Frame(self.main.win)
        frame_level.text_set("Level")
        frame_level.size_hint_align_set(-1, -1)
        box.pack_end(frame_level)
        frame_level.show()

        box_level = elementary.Box(self.main.win)
        frame_level.content_set(box_level)
        box_level.show()

        self.hs_level = elementary.Hoversel(self.main.win)
        self.hs_level.hover_parent_set(self.main.win)
        self.hs_level.size_hint_align_set(-1, 0)
        box_level.pack_end(self.hs_level)
        for level in LEVELS:
            self.hs_level.item_add(
                level,
                "arrow_right", elementary.ELM_ICON_STANDARD,
                self.change_level, level
                )
        self.hs_level.show()

        # theme
        frame_theme = elementary.Frame(self.main.win)
        frame_theme.text_set("Theme")
        frame_theme.size_hint_align_set(-1, -1)
        box.pack_end(frame_theme)
        frame_theme.show()

        box_theme = elementary.Box(self.main.win)
        frame_theme.content_set(box_theme)
        box_theme.show()

        self.hs_theme = elementary.Hoversel(self.main.win)
        self.hs_theme.hover_parent_set(self.main.win)
        self.hs_theme.size_hint_align_set(-1, 0)
        box_theme.pack_end(self.hs_theme)
        for theme in sorted(CARDS.iterkeys()):
            self.hs_theme.item_add(
                theme.capitalize(),
                "arrow_right", elementary.ELM_ICON_STANDARD,
                self.change_theme, theme
                )
        self.hs_theme.show()

        # mode
        frame_mode = elementary.Frame(self.main.win)
        frame_mode.text_set("Mode")
        frame_mode.size_hint_align_set(-1, -1)
        box.pack_end(frame_mode)
        frame_mode.show()

        box_mode = elementary.Box(self.main.win)
        frame_mode.content_set(box_mode)
        box_mode.show()

        self.hs_mode = elementary.Hoversel(self.main.win)
        self.hs_mode.hover_parent_set(self.main.win)
        self.hs_mode.size_hint_align_set(-1, 0)
        box_mode.pack_end(self.hs_mode)
        self.hs_mode.show()

        for i, mode in enumerate(MODES):
            self.hs_mode.item_add(mode, "arrow_right", elementary.ELM_ICON_STANDARD, self.change_mode, i + 1)

        # player 1
        box_player1 = elementary.Box(self.main.win)
        box_player1.homogenous_set(True)
        box_player1.size_hint_weight_set(1, 1)
        box_player1.size_hint_align_set(-1, -1)
        box_player1.horizontal_set(True)
        box_mode.pack_end(box_player1)
        box_player1.show()

        label_player1 = elementary.Label(self.main.win)
        label_player1.text_set('Player 1')
        label_player1.size_hint_weight_set(0, 0)
        label_player1.size_hint_align_set(0, 0.5)
        box_player1.pack_end(label_player1)
        label_player1.show()

        scroller_player1_name = elementary.Scroller(self.main.win)
        scroller_player1_name.content_min_limit(0, 1)
        scroller_player1_name.policy_set(elementary.ELM_SCROLLER_POLICY_OFF, elementary.ELM_SCROLLER_POLICY_OFF)
        scroller_player1_name.size_hint_weight_set(1, 0)
        scroller_player1_name.size_hint_align_set(-1, -1)
        scroller_player1_name.bounce_set(True, False)
        box_player1.pack_end(scroller_player1_name)
        scroller_player1_name.show()

        entry_player1_name = elementary.Entry(self.main.win)
        entry_player1_name.entry_set(cfg.get('player1'))
        entry_player1_name.single_line_set(True)
        entry_player1_name.size_hint_weight_set(1, 0)
        entry_player1_name.size_hint_align_set(-1, .5)
        entry_player1_name.callback_changed_add(self.change_player_name, 1)
        scroller_player1_name.content_set(entry_player1_name)
        entry_player1_name.show()

        # player 2
        box_player2 = elementary.Box(self.main.win)
        box_player2.homogenous_set(True)
        box_player2.size_hint_weight_set(1, 1)
        box_player2.size_hint_align_set(-1, -1)
        box_player2.horizontal_set(True)
        box_mode.pack_end(box_player2)
        box_player2.show()

        label_player2 = elementary.Label(self.main.win)
        label_player2.text_set('Player 2')
        label_player2.size_hint_weight_set(0, 0)
        label_player2.size_hint_align_set(0, 0.5)
        box_player2.pack_end(label_player2)
        label_player2.show()

        scroller_player2_name = elementary.Scroller(self.main.win)
        scroller_player2_name.content_min_limit(0, 1)
        scroller_player2_name.policy_set(elementary.ELM_SCROLLER_POLICY_OFF, elementary.ELM_SCROLLER_POLICY_OFF)
        scroller_player2_name.size_hint_weight_set(1, 0)
        scroller_player2_name.size_hint_align_set(-1, -1)
        scroller_player2_name.bounce_set(True, False)
        box_player2.pack_end(scroller_player2_name)
        scroller_player2_name.show()

        self.entry_player2_name = elementary.Entry(self.main.win)
        self.entry_player2_name.entry_set(cfg.get('player2'))
        self.entry_player2_name.single_line_set(True)
        self.entry_player2_name.size_hint_weight_set(1, 0)
        self.entry_player2_name.size_hint_align_set(-1, .5)
        self.entry_player2_name.callback_changed_add(self.change_player_name, 2)
        scroller_player2_name.content_set(self.entry_player2_name)
        self.entry_player2_name.show()

        # sounds
        toggle = elementary.Check(self.main.win)
        toggle.text_set("Sounds")
        toggle.style_set("toggle")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(cfg.get('sounds'))
        toggle.text_part_set("on", "Yes")
        toggle.text_part_set("off", "No")
        toggle.callback_changed_add(self.toggle_sounds)
        box.pack_end(toggle)
        toggle.show()

        # fullscreen
        toggle = elementary.Check(self.main.win)
        toggle.text_set("Fullscreen")
        toggle.style_set("toggle")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(cfg.get('fullscreen'))
        toggle.text_part_set("on", "Yes")
        toggle.text_part_set("off", "No")
        toggle.callback_changed_add(self.toggle_fullscreen)
        box.pack_end(toggle)
        toggle.show()

        # rotate
        toggle = elementary.Check(self.main.win)
        toggle.text_set("Orientation")
        toggle.style_set("toggle")
        toggle.size_hint_align_set(-1, 0)
        toggle.state_set(cfg.get('rotation'))
        toggle.text_part_set("off", "Portrait")
        toggle.text_part_set("on", "Landscape")
        toggle.callback_changed_add(self.toggle_rotation)
        box.pack_end(toggle)
        toggle.show()

        self.change_level()
        self.change_theme()
        self.change_mode()

        self.main.pager.item_simple_push(self.box)

    def change_level(self, hs = None, item = None, level = None):
        if level is None:
            level = cfg.get('level')
        else:
            cfg.set('level', level)
        self.hs_level.text_set(level)

    def change_mode(self, hs = None, item = None, mode = None):
        if mode is None:
            mode = cfg.get('mode')
        else:
            cfg.set('mode', mode)
        self.entry_player2_name.disabled_set(mode == 1)
        self.hs_mode.text_set(MODES[mode - 1])

    def change_player_name(self, entry, num):
        cfg.set('player%d' % num, entry.entry_get())

    def change_theme(self, hs = None, item = None, theme = None):
        if theme is None:
            theme = cfg.get('theme')
        else:
            cfg.set('theme', theme)
        self.hs_theme.text_set(theme.capitalize())

    def promote(self):
        if not self.box:
            self.build()
        self.main.pager.item_simple_promote(self.box)

    def toggle_fullscreen(self, toggle):
        fullscreen = toggle.state_get()
        if fullscreen != cfg.get('fullscreen'):
            self.main.win.fullscreen_set(fullscreen)
            cfg.set('fullscreen', fullscreen)

    def toggle_rotation(self, toggle):
        cfg.set('rotation', toggle.state_get())
        self.main.win.rotation_set(toggle.state_get() * 270)

    def toggle_sounds(self, toggle):
        cfg.set('sounds', toggle.state_get())


#
# High Scores
#

class HighScores(object):
    def __init__(self, main):
        self.main = main
        self.box = None

        self.path = os.path.join(os.path.expanduser("~"), ".config/%s/high_scores" % APP_NAME.lower())
        if os.path.exists(self.path):
            self.data = cPickle.load(open(self.path, 'r'))
        else:
            self.data = []

        self.level = None

    def build(self):
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        # level selector
        self.hs_level = elementary.Hoversel(self.main.win)
        self.hs_level.hover_parent_set(self.main.win)
        self.hs_level.size_hint_align_set(-1.0, 0.0)
        self.box.pack_end(self.hs_level)
        for level in LEVELS:
            self.hs_level.item_add(
                'Level %s' % level,
                "arrow_right", elementary.ELM_ICON_STANDARD,
                self.change_level, level
                )
        self.hs_level.show()

        # list
        self.list = elementary.List(self.main.win)
        self.list.size_hint_weight_set(1.0, 1.0)
        self.list.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(self.list)
        self.list.show()

        self.change_level()        

        self.main.pager.item_simple_push(self.box)

    def add(self, score):
        self.iw = elementary.InnerWindow(self.main.win)
        self.iw.show()

        box = elementary.Box(self.main.win)
        self.iw.content_set(box)
        box.show()

        label_title = elementary.Label(self.main.win)
        label_title.text_set('<b>New High Score: %d</>' % score)
        box.pack_end(label_title)
        label_title.show()

        frame = elementary.Frame(self.main.win)
        frame.text_set("Enter Your Name")
        frame.size_hint_weight_set(1, 1)
        frame.size_hint_align_set(-1, 0.5)
        box.pack_end(frame)
        frame.show()

        box_name = elementary.Box(self.main.win)
        box_name.horizontal_set(True)
        frame.content_set(box_name)
        box_name.show()

        scroller_name = elementary.Scroller(self.main.win)
        scroller_name.size_hint_weight_set(1, 1)
        scroller_name.size_hint_align_set(-1, -1)
        scroller_name.bounce_set(True, False)
        box_name.pack_end(scroller_name)
        scroller_name.show()

        self.entry_name = elementary.Entry(self.main.win)
        self.entry_name.scale_set(2)
        self.entry_name.entry_set(cfg.get('player1'))
        self.entry_name.single_line_set(True)
        self.entry_name.size_hint_weight_set(1, 1)
        self.entry_name.size_hint_align_set(-1, -1)
        scroller_name.content_set(self.entry_name)
        self.entry_name.show()
        # FIXME: Hack
        button_hack = elementary.Button(self.main.win)
        box_name.pack_end(button_hack)
        button_hack.size_hint_min_set(0, button_hack.size_hint_min[1])
        button_hack.hide()

        box_buttons = elementary.Box(self.main.win)
        box_buttons.horizontal_set(True)
        box_buttons.homogenous_set(True)
        box_buttons.size_hint_align_set(-1.0, 0)
        box.pack_end(box_buttons)
        box_buttons.show()

        button = elementary.Button(self.main.win)
        button.size_hint_weight_set(1, 0)
        button.size_hint_align_set(-1, 0)
        button.text_set('Cancel')
        button.callback_clicked_add(self.cancel_add)
        box_buttons.pack_end(button)
        button.show()

        button = elementary.Button(self.main.win)
        button.size_hint_weight_set(1, 0)
        button.size_hint_align_set(-1, 0)
        button.text_set('OK')
        button.callback_clicked_add(self.validate_add, score)
        box_buttons.pack_end(button)
        button.show()

    def cancel_add(self, button):
        self.iw.delete()

    def change_level(self, hs = None, item = None, level = None):
        if level is None:
            level = cfg.get('level')
        self.hs_level.text_set('Level %s' % level)
        self.level = level
        self.populate()

    def check_new(self, score):
        high_scores = []
        for h in self.data:
            if h['level'] == cfg.get('level'):
                high_scores.append(h['score'])
        if len(high_scores) < 10 or score < max(high_scores):
            self.add(score)

    def populate(self, *args):
        self.list.clear()

        for h in sorted(self.data, key = lambda s : s['score']):
            if h['level'] != self.level:
                continue
            label = elementary.Label(self.main.win)
            label.scale_set(2)
            label.text_set(' %d ' % h['score'])
            self.list.item_append(h['name'], label, None, None, None)

        self.list.go()

    def promote(self):
        if not self.box:
            self.build()
        self.main.pager.item_simple_promote(self.box)

    def validate_add(self, button, score):
        name = self.entry_name.entry_get().replace('<br>', '')
        if not name:
            return

        self.data.append({'name': name, 'score': score, 'level': cfg.get('level')})

        d = {}
        for h in self.data:
            if h['level'] not in d:
                d[h['level']] = []
            d[h['level']].append(h)

        self.data = []
        for level in d:
            self.data += sorted(d[level], key = lambda s : s['score'])[:10]
        cPickle.dump(self.data, open(self.path, 'w+'))

        self.iw.delete()
        self.main.show_high_scores()
        self.change_level(level = cfg.get('level'))


#
# About
#

class About(object):
    def __init__(self, main):
        self.main = main
        self.box = None

    def build(self):
        self.box = elementary.Box(self.main.win)
        self.box.size_hint_align_set(-1, -1)
        self.box.show()

        label = elementary.Label(self.main.win)
        label.text_set('<b>%s %s</>' % (APP_NAME, APP_VERSION))
        label.size_hint_align_set(0.5, 0.5)
        self.box.pack_end(label)
        label.show()

        scroller = elementary.Scroller(self.main.win)
        scroller.bounce_set(False, True)
        scroller.size_hint_weight_set(1.0, 1.0)
        scroller.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(scroller)
        scroller.show()

        box = elementary.Box(self.main.win)
        box.size_hint_weight_set(1, 1)
        scroller.content_set(box)
        box.show()

        about  = """\
<b>Minneo</> is a classic Memory game.

It's a game of memory and concentration.

<b>Copyright</> © 2010-2012 Valéry Febvre

<b>Licensed</> under the GNU GPL v3

<b>Homepage</> http://code.google.com/p/minneo/

If you like this program send me an email to vfebvre@easter-eggs.com\
"""

        entry = elementary.Entry(self.main.win)
        entry.editable_set(False)
        entry.line_wrap_set(True)
        entry.size_hint_align_set(-1, -1)
        entry.entry_set(about.replace('\n', '<br>'))
        box.pack_end(entry)
        entry.show()

        self.main.pager.item_simple_push(self.box)

    def promote(self):
        if not self.box:
            self.build()
        self.main.pager.item_simple_promote(self.box)


class Minneo(object):
    def __init__(self):
        self.pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
        self.pcm.setchannels(1)
        self.pcm.setrate(44100)
        self.pcm.setformat(alsaaudio.PCM_FORMAT_S16_LE)

        self.win = elementary.Window(APP_NAME, elementary.ELM_WIN_BASIC)
        self.win.title_set(APP_NAME)
        self.win.fullscreen_set(cfg.get('fullscreen'))
        self.win.rotation_set(cfg.get('rotation') * 270)
        self.win.callback_destroy_add(self.quit)

        bg = elementary.Background(self.win)
        self.win.resize_object_add(bg)
        bg.size_hint_weight_set(1, 1)
        bg.show()

        self.box = elementary.Box(self.win)
        self.box.size_hint_weight_set(1.0, 1.0)
        self.win.resize_object_add(self.box)
        self.box.show()

        toolbar = elementary.Toolbar(self.win)
        toolbar.homogenous_set(False)
        toolbar.menu_parent_set(self.win)
        toolbar.size_hint_align_set(-1.0, 0)
        self.box.pack_end(toolbar)
        toolbar.show()

        self.toolbar_item_playground = toolbar.item_append('playground', "Playground", self.show_playground)
        self.toolbar_item_settings = toolbar.item_append('settings', "Settings", self.show_settings)
        self.toolbar_item_high_scores = toolbar.item_append('high-scores', "High Scores", self.show_high_scores)
        self.toolbar_item_about = toolbar.item_append('about', "About", self.show_about)

        self.pager = elementary.Naviframe(self.win)
        self.pager.size_hint_weight_set(1.0, 1.0)
        self.pager.size_hint_align_set(-1.0, -1.0)
        self.box.pack_end(self.pager)
        self.pager.show()

        self.playground = Playground(self)
        self.settings = Settings(self)
        self.high_scores = HighScores(self)
        self.about = About(self)

        self.show_playground()

        self.win.resize(480, 640)
        self.win.show()

    def quit(self, *args):
        self.pcm.close()
        # save settings
        cfg.save()
        # exit
        elementary.exit()

    def show_playground(self, *args):
        self.toolbar_item_playground.selected = True
        self.playground.promote()

    def show_settings(self, *args):
        self.toolbar_item_settings.selected = True
        self.settings.promote()

    def show_about(self, *args):
        self.about.promote()

    def show_high_scores(self, *args):
        self.toolbar_item_high_scores.selected = True
        self.high_scores.promote()


def main():
    elementary.init()
    elementary.theme_overlay_add(GUI_THEME_EDJ)
    Minneo()
    elementary.run()
    elementary.shutdown()
    return 0

if __name__ == "__main__":
    exit(main())
